/*
 * File:                menu-browser.c
 * Created:             September 2005
 * Created by:          Axel von Bertoldi
 * Last Modified:       November 2009
 * Last Modified by:    Axel von Bertoldi
 * (C) 2005-2009        Axel von Bertoldi
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to:
 * The Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor
 * Boston, MA 02110-1301, USA.
 */

#include <glib/gprintf.h>
#include <gdk/gdkkeysyms.h>

#include "menu-browser.h"
#include "utils.h"
#include "vfs.h"
#include "context-menu.h"
#include "config.h"

/******************************************************************************/
#define MAX_FILE_NAME_LENGTH 30 /* What's a good value here??? */
#define G_OBJECT_DATA_NAME "item_path"
/******************************************************************************/
struct _MenuBrowserPrivate {
    GtkWidget       *menu_item_label;
    GtkMenuShell    *parent_menu_shell;
    Garbage         garbage;
};
#define MENU_BROWSER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TYPE_MENU_BROWSER, MenuBrowserPrivate))
enum  {
    MENU_BROWSER_DUMMY_PROPERTY
};
/******************************************************************************/
static GtkTargetEntry menu_item_target[] = {
    { "text/uri-list", GTK_TARGET_OTHER_APP, 0 }
};
/******************************************************************************/
static gpointer menu_browser_parent_class = NULL;
static void menu_browser_dispose (GObject * obj);
static void menu_browser_populate_menu (GtkWidget *parent_menu_item, MenuBrowser *self);
/******************************************************************************/
static void
menu_browser_clear_menu (GtkWidget *menu_item) {
    GtkWidget *menu = menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu_item));
    gtk_container_foreach (GTK_CONTAINER (menu), (GtkCallback) gtk_widget_destroy, NULL);
}
/******************************************************************************/
static void
menu_browser_clean_up (MenuBrowser *self) {
    g_assert (IS_MENU_BROWSER (self));

    menu_browser_clear_menu (GTK_WIDGET (self));

    garbage_empty (&(self->priv->garbage), TRUE);
}
/******************************************************************************/
static void
menu_browser_on_dir_middle_click (const gchar *path, MenuBrowser *self) {
    g_assert (IS_MENU_BROWSER (self));

    gchar **args = g_strv_new (ARGS_SIZE);
    args[ARG_APP]  = g_strdup (self->prefs->terminal);
    args[ARG_FILE] = g_strdup (path);
    vfs_launch_application ((const gchar*const*)args);
    g_strfreev (args);
}
/******************************************************************************/
static void
menu_browser_on_file_left_click (const gchar *file_name_and_path, MenuBrowser *self) {
    g_assert (IS_MENU_BROWSER (self));

    vfs_file_do_default_action (file_name_and_path);
}
/******************************************************************************/
static void
menu_browser_on_file_middle_click (const gchar *file_name_and_path, MenuBrowser *self) {
    g_assert (IS_MENU_BROWSER (self));

    gchar **args = g_strv_new (ARGS_SIZE);
    args[ARG_APP]  = g_strdup (self->prefs->editor);
    args[ARG_FILE] = g_strdup (file_name_and_path);
    vfs_launch_application ((const gchar*const*)args);
    g_strfreev (args);
}
/******************************************************************************/
static gboolean
menu_browser_on_file_right_click (const gchar *file_name_and_path,
                                  GtkWidget *menu_item,
                                  MenuBrowser *self) {
    g_assert (IS_MENU_BROWSER (self));
#ifdef ENABLE_CONTEXT_MENU
    ContextMenuPrefs prefs;
    prefs.terminal = self->prefs->terminal; 
    prefs.editor = self->prefs->editor;
    return context_menu_display (file_name_and_path, menu_item, prefs);
#else
    utils_show_dialog (_("Error"),
                       _("Right-click on file action not yet implemented."),
                       GTK_MESSAGE_INFO);
    return TRUE;
#endif
}
/******************************************************************************/
static void
menu_browser_on_item_activate (GtkMenuItem *menu_item, MenuBrowser *self) {
    g_assert (IS_MENU_BROWSER (self));

    gchar *path = (gchar*)g_object_get_data (G_OBJECT (menu_item), G_OBJECT_DATA_NAME);

    if (!vfs_file_exists (path)) {
        gchar *tmp = g_strdup_printf (_("Error: The file \"%s\" does not exists."), path);
        utils_show_dialog (_("Error"), tmp, GTK_MESSAGE_ERROR);
        g_free (tmp);
        return;
    }

    menu_browser_on_file_left_click (path, self);
}
/******************************************************************************/
static gboolean
menu_browser_on_item_button_release (GtkWidget *menu_item,
                                     GdkEventButton *event,
                                     MenuBrowser *self) {
    g_assert (IS_MENU_BROWSER (self));

    gchar *path = (gchar*)g_object_get_data (G_OBJECT (menu_item), G_OBJECT_DATA_NAME);

    if (!vfs_file_exists (path)) {
        gchar *tmp = g_strdup_printf (_("Error: The file \"%s\" does not exists."), path);
        utils_show_dialog (_("Error"), tmp, GTK_MESSAGE_ERROR);
        g_free (tmp);
        return FALSE;
    }
    
    if (event->button == 2) {
        vfs_file_is_directory (path) ?
            menu_browser_on_dir_middle_click (path, self) :
            menu_browser_on_file_middle_click (path, self);
        gtk_menu_shell_deactivate (self->priv->parent_menu_shell);
        return TRUE;
    }
    else if (event->button == 3) {
        /* Sigh! Have to set the event button to 0 because the menu items in
         * the "open with" sub menu in the context menu aren't activated
         * otherwise. This bug appeared when "button_press_event" was changed
         * to "button_release_event" in all the mouse clicks on the menu items
         * of the main menu. */
        event->button = 0;

        g_object_set_data (G_OBJECT (menu_item), "menu_browser", self);
        g_object_set_data (G_OBJECT (menu_item), "button_event", event);
        return menu_browser_on_file_right_click (path, menu_item, self);
    }

    return FALSE;
}
/******************************************************************************/
static gboolean
menu_browser_on_menu_key_release (GtkWidget *menu,
                                  GdkEventKey *event,
                                  MenuBrowser *self) {
    g_assert (IS_MENU_BROWSER (self));

    GtkMenuShell *menu_shell = GTK_MENU_SHELL (menu);
    GtkWidget *menu_item = menu_shell->active_menu_item;

    /* huh!? */
    if (!self->priv->parent_menu_shell->active || !menu_item) {
        return FALSE;
    }

    gchar *path = (gchar*)g_object_get_data (G_OBJECT (menu_item), G_OBJECT_DATA_NAME);

    if (!vfs_file_exists (path)) {
        gchar *tmp = g_strdup_printf ("Error: The file \"%s\" does not exists.", path);
        utils_show_dialog (_("Error"), tmp, GTK_MESSAGE_ERROR);
        g_free (tmp);
        return FALSE;
    }

    /* "Enter" or equivalent a are handled by menu_browser_on_item_activate */

    if (event->keyval == GDK_F4 ||
             event->keyval == GDK_KP_F4) {

        vfs_file_is_directory (path) ?
            menu_browser_on_dir_middle_click (path, self) :
            menu_browser_on_file_middle_click (path, self);
        gtk_menu_shell_deactivate (self->priv->parent_menu_shell);
        return TRUE;
    }
    else if ((event->keyval == GDK_Menu) ||
             (event->keyval == GDK_F10 &&
             (event->state & gtk_accelerator_get_default_mod_mask ()) == GDK_SHIFT_MASK)){
        g_object_set_data (G_OBJECT (menu_item), "menu_browser", self);
        g_object_set_data (G_OBJECT (menu_item), "button_event", event);
        return menu_browser_on_file_right_click (path, menu_item, self);
    }
    return FALSE;
}
/******************************************************************************/
/*static void*/
/*on_drag_end (GtkWidget *widget,*/
             /*GdkDragContext *context,*/
             /*gpointer user_data) {*/
    /*[> ??? <]*/
/*}*/
/******************************************************************************/
static void
on_drag_data_get (GtkWidget *widget,
                  GdkDragContext *context,
                  GtkSelectionData *selection_data,
                  guint target_type,
                  guint time,
                  const char *uri) {

    gtk_selection_data_set (selection_data,
                            selection_data->target,
                            8,
                            (guchar *)uri,
                            strlen(uri));
}
/******************************************************************************/
static void
on_drag_begin (GtkWidget *menu_item, GdkDragContext *context) {

    GIcon *icon = NULL;
    GtkIconSize size;
    const gchar *icon_name = NULL;
    gchar *tmp = NULL;

    GtkWidget *image = gtk_image_menu_item_get_image (GTK_IMAGE_MENU_ITEM (menu_item));
    gtk_image_get_gicon (GTK_IMAGE(image), &icon, &size);

    if (G_IS_FILE_ICON(icon)) {
        /* What do I do now!? */
        GFile *file = g_file_icon_get_file(G_FILE_ICON(icon));
        gchar *tmp1 = g_file_get_basename (file);
        tmp = g_strndup (tmp1, strlen(tmp1) - 4);
        icon_name = (const gchar*)tmp;
        g_free(tmp1);
    }
    else if (G_IS_THEMED_ICON(icon)) {
        /* ugly */
        const char*const* icon_names = g_themed_icon_get_names (G_THEMED_ICON(icon));
        icon_name = icon_names ? icon_names[0] : NULL;
    }

    if (icon_name) {
        /* Sigh! gtk_drag_source_set_icon_name can't load drag icon from icon
         * name inode-directory "i.e. folders". Since that will probably be the
         * most common case, make sure it looks OK. */
        if (strcmp(icon_name, "inode-directory") == 0)
            icon_name = "folder";

        gtk_drag_source_set_icon_name (menu_item, icon_name);
    }
    g_free(tmp);
}
/******************************************************************************/
static void
menu_browser_dnd_setup (GtkWidget *menu_item) {
    gchar *file_name = g_object_get_data (G_OBJECT (menu_item), G_OBJECT_DATA_NAME);
    gchar *uri = g_filename_to_uri(file_name, NULL, NULL);

    gtk_drag_source_set (menu_item,
                         GDK_BUTTON1_MASK,
                         menu_item_target,
                         1,
                         GDK_ACTION_MOVE | GDK_ACTION_ASK);

    g_signal_connect_data (menu_item,
                           "drag_data_get",
                           G_CALLBACK (on_drag_data_get),
                           uri,
                           (GClosureNotify)g_free,
                            0);

    g_signal_connect (menu_item,
                      "drag_begin",
                      G_CALLBACK (on_drag_begin),
                      NULL);
    /*g_signal_connect (menu_item,*/
                      /*"drag_end",*/
                      /*G_CALLBACK (on_drag_end),*/
                      /*NULL);*/
}
/******************************************************************************/
static void
menu_browser_add_signals (GtkWidget *menu_item, MenuBrowser *self) {
    g_assert (IS_MENU_BROWSER (self));

    g_signal_connect (menu_item,
                      "button_release_event",
                      G_CALLBACK (menu_browser_on_item_button_release),
                      self);

    g_signal_connect (GTK_MENU_ITEM (menu_item),
                      "activate",
                      G_CALLBACK (menu_browser_on_item_activate),
                      self);

    menu_browser_dnd_setup (menu_item);
}
/******************************************************************************/
static GtkWidget*
menu_browser_entry_new (VfsFileInfo *file_info, MenuBrowser *self) {
    g_assert (IS_MENU_BROWSER (self));

    /* Make a menu item for this file/dir, limit the length of the text in the
     * menu WTH is going on here! Because we're using a mnemonic, we have to
     * replace all _ with __ for the name to appear correctly. */
    gchar *tmp_str = utils_escape_str (file_info->display_name, "_", "__");
    gchar *escaped_str = g_strdup_printf ("_%s", tmp_str);
    GtkWidget *menu_item = gtk_image_menu_item_new_with_mnemonic (escaped_str);
    g_free (escaped_str);
    g_free (tmp_str);

    /* set the ellipsizig and tooltip */
    GtkWidget *label = gtk_bin_get_child (GTK_BIN (menu_item));
    if (GTK_IS_LABEL (label)) {
        gtk_label_set_max_width_chars (GTK_LABEL (label), MAX_FILE_NAME_LENGTH);    
        gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_MIDDLE);
    }
    gchar *tooltip = g_strdup_printf ("%s - %s",
                                      file_info->display_name,
                                      file_info->size);
    gtk_widget_set_tooltip_markup (menu_item, tooltip);
    g_free (tooltip);

    /* bold executable files */
    if (file_info->is_executable) {
        gchar *markup = g_markup_printf_escaped ("<span weight=\"bold\">%s</span>",
                                                 file_info->display_name);
        gtk_label_set_markup (GTK_LABEL (label), markup);
        g_free (markup);
    }

    /*stick the icon in the menu item, the menu item in the menu and show it all*/
    gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item),
                                   file_info->icon);

    garbage_add_item (self->priv->garbage, file_info->file_name);
    garbage_add_item (self->priv->garbage, file_info->display_name);
    garbage_add_item (self->priv->garbage, file_info->size);

    g_object_set_data (G_OBJECT (menu_item),
                       G_OBJECT_DATA_NAME,
                       file_info->file_name);
    return menu_item;
}
/******************************************************************************/
static void
menu_browser_add_files (GtkWidget *menu, GPtrArray *files, MenuBrowser *self) {
    g_assert (IS_MENU_BROWSER (self));

    int i;
    for (i=0; i<files->len; i++) {
        VfsFileInfo *vfs_file_info = (VfsFileInfo*)g_ptr_array_index (files, i);

        GtkWidget *menu_item = menu_browser_entry_new (vfs_file_info, self);

        gtk_menu_shell_append (GTK_MENU_SHELL(menu), menu_item);

        menu_browser_add_signals (menu_item, self);
    }
}
/******************************************************************************/
static void
menu_browser_add_folders (GtkWidget *menu, GPtrArray *dirs, MenuBrowser *self) {
    g_assert (IS_MENU_BROWSER (self));

    int i;
    for (i=0; i<dirs->len; i++) {
        VfsFileInfo *vfs_file_info = (VfsFileInfo*)g_ptr_array_index (dirs, i);

        GtkWidget *menu_item = menu_browser_entry_new (vfs_file_info, self);

        gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
        /*make the sub menu to show all the files in this dir*/
        gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item), gtk_menu_new ());

        g_signal_connect (GTK_MENU_ITEM (menu_item),
                          "activate",
                          G_CALLBACK (menu_browser_populate_menu),
                          self);

        /* This code is responsible for bug #71.
         * See http://code.google.com/p/gnome-menu-file-browser-applet/issues/detail?id=71 */
        /*g_signal_connect (GTK_WIDGET (menu_item),*/
                          /*"button_press_event",*/
                          /*G_CALLBACK (menu_browser_on_item_button_release),*/
                          /*self);*/
    }
}
/******************************************************************************/
static GtkWidget*
menu_browser_add_menu_header (GtkWidget *menu, gchar *path, MenuBrowser *self) {
    g_assert (IS_MENU_BROWSER (self));

    VfsFileInfo *vfs_file_info   = g_new0 (VfsFileInfo, 1);
    vfs_file_info->display_name  = g_strdup (g_path_get_basename(path));
    vfs_file_info->file_name     = g_strdup (path);
    vfs_file_info->icon          = vfs_get_icon_for_file (path);
    vfs_file_info->is_executable = FALSE;

    GtkWidget *menu_item = menu_browser_entry_new (vfs_file_info, self);
    g_free (vfs_file_info);

    gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);

    menu_browser_add_signals (menu_item, self);

    gtk_menu_shell_append (GTK_MENU_SHELL (menu),
                           gtk_separator_menu_item_new());
    return menu_item;
}
/******************************************************************************/
static void
menu_browser_populate_menu (GtkWidget *parent_menu_item, MenuBrowser *self) {
    g_assert (IS_MENU_BROWSER (self));

    gchar       *current_path = NULL;
    gchar       *error = NULL;
    GtkWidget   *menu_item = NULL;
    GtkWidget   *current_menu = NULL;
    GPtrArray   *files = g_ptr_array_new();
    GPtrArray   *dirs = g_ptr_array_new();

    /* empty the menu of its existing contents first */
    menu_browser_clear_menu (parent_menu_item);

    current_path = (gchar*)g_object_get_data (G_OBJECT (parent_menu_item), G_OBJECT_DATA_NAME);

    /* get the menu widget to pack all the menu items for this dir into */
    current_menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (parent_menu_item));

    /* If we want to catch key presses/releases, we have to do it before
     * menu_item signals are emitted. So, we have to watch the menu. */
    g_signal_connect (current_menu,
                      "key_release_event",
                      G_CALLBACK (menu_browser_on_menu_key_release),
                      self);

    /* add the dir name and events */
    GtkWidget *menu_header = menu_browser_add_menu_header (current_menu, current_path, self);

    /* read the contents of the dir. */
    error = vfs_get_dir_listings (files, dirs,
                                  self->prefs->show_hidden,
                                  self->prefs->show_thumbnail,
                                  self->prefs->hide_files,
                                  current_path);

    gchar *display_name  = g_strdup (g_path_get_basename(current_path));
    gchar *tooltip = g_strdup_printf ("%s - %d item%s", display_name, files->len + dirs->len,
                                      ((dirs->len + files->len) > 1 ? "s" : ""));
    gtk_widget_set_tooltip_text (parent_menu_item, tooltip);
    gtk_widget_set_tooltip_text (menu_header, tooltip);
    g_free (tooltip);
    g_free (display_name);

    /* add the folders*/
    menu_browser_add_folders (current_menu, dirs, self);

    if ((dirs->len > 0) & (files->len > 0)) {
        /*add a separator between dirs and files*/
        gtk_menu_shell_append (GTK_MENU_SHELL (current_menu),
                               gtk_separator_menu_item_new());
    }

    /* add the files */
    menu_browser_add_files (current_menu, files, self);

    /* add any error or other messages */
    if (error != NULL) {
        menu_item = gtk_menu_item_new_with_label (error);
        g_free (error);
        gtk_menu_shell_append (GTK_MENU_SHELL (current_menu), menu_item);
        gtk_widget_set_sensitive (GTK_WIDGET (menu_item), FALSE);
    } /* the folder was empty */
    else if ((dirs->len == 0) & (files->len == 0)) {
        menu_item = gtk_menu_item_new_with_label (_("(Empty)"));
        gtk_menu_shell_append (GTK_MENU_SHELL (current_menu), menu_item);
        gtk_widget_set_sensitive (GTK_WIDGET (menu_item), FALSE);
    }
    gtk_widget_show_all (current_menu);

    /* clean up */
    g_ptr_array_foreach (dirs, (GFunc)g_free, NULL);
    g_ptr_array_foreach (files, (GFunc)g_free, NULL);
    g_ptr_array_free (dirs, TRUE);
    g_ptr_array_free (files, TRUE);
}
/******************************************************************************/
void
menu_browser_update (MenuBrowser *self, const gchar *path, const gchar *label) {
    g_assert (IS_MENU_BROWSER (self));

    GtkWidget *item_label = GTK_BIN (self)->child;
    gtk_label_set_text (GTK_LABEL (item_label), label);

    g_free (g_object_get_data (G_OBJECT (self), G_OBJECT_DATA_NAME));
    g_object_set_data (G_OBJECT (self), G_OBJECT_DATA_NAME, g_strdup (path));
}
/******************************************************************************/
static void
menu_browser_class_init (MenuBrowserClass *klass) {
    menu_browser_parent_class = g_type_class_peek_parent (klass);
    g_type_class_add_private (klass, sizeof (MenuBrowserPrivate));
    G_OBJECT_CLASS (klass)->dispose = menu_browser_dispose;
}
/******************************************************************************/
static void
menu_browser_init (MenuBrowser *self) {
    g_assert (IS_MENU_BROWSER (self));

    self->priv = MENU_BROWSER_GET_PRIVATE (self);
}
/******************************************************************************/
static void
menu_browser_dispose (GObject *obj) {
    MenuBrowser *self = MENU_BROWSER (obj);
/*  MenuBrowserClass *klass = MENU_BROWSER_CLASS (g_type_class_peek (TYPE_MENU_BROWSER));*/

    (self->priv->menu_item_label == NULL ? NULL : (self->priv->menu_item_label = (gtk_widget_destroy (self->priv->menu_item_label), NULL)));
    (self->priv->garbage == NULL ? NULL : garbage_empty (&(self->priv->garbage), FALSE));

    G_OBJECT_CLASS (menu_browser_parent_class)->dispose (obj);
}
/******************************************************************************/
GType
menu_browser_get_type (void) {
    static GType menu_browser_type_id = 0;
    if (G_UNLIKELY (menu_browser_type_id == 0)) {
        static const GTypeInfo g_define_type_info = {sizeof (MenuBrowserClass),
                                                    (GBaseInitFunc) NULL,
                                                    (GBaseFinalizeFunc) NULL,
                                                    (GClassInitFunc) menu_browser_class_init,
                                                    (GClassFinalizeFunc) NULL,
                                                    NULL,
                                                    sizeof (MenuBrowser),
                                                    0,
                                                    (GInstanceInitFunc) menu_browser_init };

        menu_browser_type_id = g_type_register_static (GTK_TYPE_IMAGE_MENU_ITEM,
                                                       "MenuBrowser",
                                                       &g_define_type_info, 0);
    }
    return menu_browser_type_id;
}
/******************************************************************************/
static gboolean
menu_browser_activate_main_menu (MenuBrowser *self) {
    g_assert (IS_MENU_BROWSER (self));

    menu_browser_clean_up (self);

    self->priv->parent_menu_shell =
            GTK_MENU_SHELL (gtk_widget_get_parent (GTK_WIDGET (self)));

    GtkWidget *menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (self));

    /* this is here because of the solution to the focus problem caused by the
     * popped context menu */
    gtk_widget_set_sensitive (GTK_WIDGET (menu), TRUE);

    menu_browser_populate_menu (GTK_WIDGET (self), self);

    gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
    /*gtk_menu_reposition (GTK_MENU (menu));*/
    return TRUE;
}
/******************************************************************************/
GtkWidget*
menu_browser_new (const gchar* path,
                  const gchar* label,
                  BrowserPrefs *prefs) {
    MenuBrowser *self = g_object_newv (TYPE_MENU_BROWSER, 0, NULL);

    self->priv->parent_menu_shell = NULL;

    if (prefs == NULL) {
        /* get the default config if none passed. */
    /*  prefs = preferences_browser_get_default ();*/
    }
    self->prefs = prefs;

    garbage_init(&(self->priv->garbage));

    GtkWidget *item_label = gtk_label_new (label);
    gtk_misc_set_alignment (GTK_MISC (item_label), 0.0, 0.5);
    gtk_container_add (GTK_CONTAINER (self), item_label);
    self->priv->menu_item_label = item_label;

    /*make the main menu*/
    gtk_menu_item_set_submenu (GTK_MENU_ITEM (self), gtk_menu_new());

    g_object_set_data (G_OBJECT (self), G_OBJECT_DATA_NAME, g_strdup (path));

    g_signal_connect_after (GTK_MENU_ITEM (self),
                            "activate",
                            G_CALLBACK (menu_browser_activate_main_menu),
                            NULL);

    /*g_signal_connect_swapped (GTK_ITEM (self),*/
                              /*"deselect",*/
                              /*G_CALLBACK (menu_browser_clean_up),*/
                              /*self);*/

    menu_browser_populate_menu (GTK_WIDGET (self), self);

    return GTK_WIDGET (self);
}
/******************************************************************************/
